
<!doctype html>
<html lang="en" class="no-js">
  <head>
    
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1">
      
      
      
      <link rel="icon" href="../../../../static/images/favicon.png">
      <meta name="generator" content="mkdocs-1.3.0, mkdocs-material-8.2.8">
    
    
      
        <title>Etcd + VScode 源码调试 + 生产部署 - WL4G DOCS</title>
      
    
    
      <link rel="stylesheet" href="../../../../assets/stylesheets/main.644de097.min.css">
      
        
        <link rel="stylesheet" href="../../../../assets/stylesheets/palette.e6a45f82.min.css">
        
      
    
    
    
      
        
        
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
        <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
      
    
    
      <link rel="stylesheet" href="../../../../static/css/util.css">
    
    <script>__md_scope=new URL("../../../..",location),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
    
      

    
    
  </head>
  
  
    
    
      
    
    
    
    
    <body dir="ltr" data-md-color-scheme="default" data-md-color-primary="" data-md-color-accent="">
  
    
    
      <script>var palette=__md_get("__palette");if(palette&&"object"==typeof palette.color)for(var key of Object.keys(palette.color))document.body.setAttribute("data-md-color-"+key,palette.color[key])</script>
    
    <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
    <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
    <label class="md-overlay" for="__drawer"></label>
    <div data-md-component="skip">
      
        
        <a href="#etcd-vscode" class="md-skip">
          Skip to content
        </a>
      
    </div>
    <div data-md-component="announce">
      
    </div>
    
      <div data-md-component="outdated" hidden>
        <aside class="md-banner md-banner--warning">
          
        </aside>
      </div>
    
    
      

<header class="md-header" data-md-component="header">
  <nav class="md-header__inner md-grid" aria-label="Header">
    <a href="../../../.." title="WL4G DOCS" class="md-header__button md-logo" aria-label="WL4G DOCS" data-md-component="logo">
      
  <img src="../../../../static/images/mylogo.jpeg" alt="logo">

    </a>
    <label class="md-header__button md-icon" for="__drawer">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/></svg>
    </label>
    <div class="md-header__title" data-md-component="header-title">
      <div class="md-header__ellipsis">
        <div class="md-header__topic">
          <span class="md-ellipsis">
            WL4G DOCS
          </span>
        </div>
        <div class="md-header__topic" data-md-component="header-topic">
          <span class="md-ellipsis">
            
              Etcd + VScode 源码调试 + 生产部署
            
          </span>
        </div>
      </div>
    </div>
    
      <form class="md-header__option" data-md-component="palette">
        
          
          
          <input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="" data-md-color-accent=""  aria-label="Switch to dark mode"  type="radio" name="__palette" id="__palette_1">
          
            <label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 6H7c-3.31 0-6 2.69-6 6s2.69 6 6 6h10c3.31 0 6-2.69 6-6s-2.69-6-6-6zm0 10H7c-2.21 0-4-1.79-4-4s1.79-4 4-4h10c2.21 0 4 1.79 4 4s-1.79 4-4 4zM7 9c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
            </label>
          
        
          
          
          <input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="" data-md-color-accent=""  aria-label="Switch to light mode"  type="radio" name="__palette" id="__palette_2">
          
            <label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h10a5 5 0 0 0 5-5 5 5 0 0 0-5-5m0 8a3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3z"/></svg>
            </label>
          
        
      </form>
    
    
      <div class="md-header__option">
        <div class="md-select">
          
          <button class="md-header__button md-icon" aria-label="Select language">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04M18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12m-2.62 7 1.62-4.33L19.12 17h-3.24z"/></svg>
          </button>
          <div class="md-select__inner">
            <ul class="md-select__list">
              
                <li class="md-select__item">
                  <a href="/en/" hreflang="en" class="md-select__link">
                    English
                  </a>
                </li>
                
                <li class="md-select__item">
                  <a href="/zh/" hreflang="zh" class="md-select__link">
                    简体中文
                  </a>
                </li>
                
            </ul>
          </div>
        </div>
      </div>
    
    
      <label class="md-header__button md-icon" for="__search">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
      </label>
      <div class="md-search" data-md-component="search" role="dialog">
  <label class="md-search__overlay" for="__search"></label>
  <div class="md-search__inner" role="search">
    <form class="md-search__form" name="search">
      <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
      <label class="md-search__icon md-icon" for="__search">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg>
      </label>
      <nav class="md-search__options" aria-label="Search">
        
        <button type="reset" class="md-search__icon md-icon" aria-label="Clear" tabindex="-1">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
        </button>
      </nav>
      
        <div class="md-search__suggest" data-md-component="search-suggest"></div>
      
    </form>
    <div class="md-search__output">
      <div class="md-search__scrollwrap" data-md-scrollfix>
        <div class="md-search-result" data-md-component="search-result">
          <div class="md-search-result__meta">
            Initializing search
          </div>
          <ol class="md-search-result__list"></ol>
        </div>
      </div>
    </div>
  </div>
</div>
    
    
  </nav>
  
</header>
    
    <div class="md-container" data-md-component="container">
      
      
        
          
            
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
  <div class="md-tabs__inner md-grid">
    <ul class="md-tabs__list">
      
        
  
  


  
  
  
    <li class="md-tabs__item">
      <a href="../../../.." class="md-tabs__link">
        Getting Started
      </a>
    </li>
  

      
    </ul>
  </div>
</nav>
          
        
      
      <main class="md-main" data-md-component="main">
        <div class="md-main__inner md-grid">
          
            
              
              <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
                <div class="md-sidebar__scrollwrap">
                  <div class="md-sidebar__inner">
                    

  


  

<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" aria-label="Navigation" data-md-level="0">
  <label class="md-nav__title" for="__drawer">
    <a href="../../../.." title="WL4G DOCS" class="md-nav__button md-logo" aria-label="WL4G DOCS" data-md-component="logo">
      
  <img src="../../../../static/images/mylogo.jpeg" alt="logo">

    </a>
    WL4G DOCS
  </label>
  
  <ul class="md-nav__list" data-md-scrollfix>
    
      
      
      

  
  
  
    
    <li class="md-nav__item md-nav__item--nested">
      
      
        <input class="md-nav__toggle md-toggle" data-md-toggle="__nav_1" data-md-state="indeterminate" type="checkbox" id="__nav_1" checked>
      
      
      
      
        <label class="md-nav__link" for="__nav_1">
          Getting Started
          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <nav class="md-nav" aria-label="Getting Started" data-md-level="1">
        <label class="md-nav__title" for="__nav_1">
          <span class="md-nav__icon md-icon"></span>
          Getting Started
        </label>
        <ul class="md-nav__list" data-md-scrollfix>
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../../.." class="md-nav__link">
        Introduction
      </a>
    </li>
  

            
          
            
              
  
  
  
    <li class="md-nav__item">
      <a href="../../../../ABOUT_CN/" class="md-nav__link">
        About
      </a>
    </li>
  

            
          
        </ul>
      </nav>
    </li>
  

    
  </ul>
</nav>
                  </div>
                </div>
              </div>
            
            
          
          <div class="md-content" data-md-component="content">
            <article class="md-content__inner md-typeset">
              
                


<h1 id="etcd-vscode">Etcd + VScode 源码调试 + 生产部署<a class="headerlink" href="#etcd-vscode" title="Permanent link">&para;</a></h1>
<h2 id="1">1. 开发环境搭建<a class="headerlink" href="#1" title="Permanent link">&para;</a></h2>
<p>背景驱动：</p>
<blockquote>
<p>新客户的服务器需要部署 <a href="https://github.com/kubernetes/kubernetes"><code>kubernetes</code></a> 集群，在安装完 etcd 后发现启动报错：</p>
</blockquote>
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1"></a><a href="#__codelineno-0-1"><span class="linenos" data-linenos="1 "></span></a>{&quot;level&quot;:&quot;fatal&quot;,&quot;ts&quot;:&quot;2021-08-25T16:24:00.044+0800&quot;,&quot;caller&quot;:&quot;etcdmain/etcd.go:203&quot;,&quot;msg&quot;:&quot;discovery failed&quot;,&quot;error&quot;:&quot;couldn&#39;t find local name \&quot;default\&quot; in the initial cluster configuration&quot;,&quot;stacktrace&quot;:&quot;etcd/server/v3/etcdmain.startEtcdOrProxyV2\n\tetcd/server/etcdmain/etcd.go:203\netcd/server/v3/etcdmain.Main\n\tetcd/server/etcdmain/main.go:40\nmain.main\n\tetcd/server/main.go:32\nruntime.main\n\truntime/proc.go:225&quot;}
</code></pre></div>
<p>或</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1"></a><a href="#__codelineno-1-1"><span class="linenos" data-linenos="1 "></span></a>{&quot;level&quot;:&quot;fatal&quot;,&quot;ts&quot;:&quot;2021-08-25T16:24:00.044+0800&quot;,&quot;caller&quot;:&quot;etcdmain/etcd.go:203&quot;,&quot;msg&quot;:&quot;discovery failed&quot;,&quot;error&quot;:&quot;cannot fetch cluster info from peer urls: could not retrieve cluster information from the given URLs&quot;,&quot;stacktrace&quot;:&quot;etcd/server/v3/etcdmain.startEtcdOrProxyV2\n\tetcd/server/etcdmain/etcd.go:203\netcd/server/v3/etcdmain.Main\n\tetcd/server/etcdmain/main.go:40\nmain.main\n\tetcd/server/main.go:32\nruntime.main\n\truntime/proc.go:225&quot;}
</code></pre></div>
<h3 id="11-vscode">1.1 生成 vscode 调试配置文件<a class="headerlink" href="#11-vscode" title="Permanent link">&para;</a></h3>
<div class="highlight"><pre><span></span><code><a id="__codelineno-2-1" name="__codelineno-2-1"></a><a href="#__codelineno-2-1"><span class="linenos" data-linenos=" 1 "></span></a><span class="nb">cd</span> <span class="nv">$PROJECT_HOME</span>
<a id="__codelineno-2-2" name="__codelineno-2-2"></a><a href="#__codelineno-2-2"><span class="linenos" data-linenos=" 2 "></span></a>sudo mkdir -p .vscode
<a id="__codelineno-2-3" name="__codelineno-2-3"></a><a href="#__codelineno-2-3"><span class="linenos" data-linenos=" 3 "></span></a>sudo cat <span class="s">&lt;&lt;-&#39;EOF&#39; &gt; ./vscode/launch.json</span>
<a id="__codelineno-2-4" name="__codelineno-2-4"></a><a href="#__codelineno-2-4"><span class="linenos" data-linenos=" 4 "></span></a><span class="s">{</span>
<a id="__codelineno-2-5" name="__codelineno-2-5"></a><a href="#__codelineno-2-5"><span class="linenos" data-linenos=" 5 "></span></a><span class="s">    // Use IntelliSense to learn about possible attributes.</span>
<a id="__codelineno-2-6" name="__codelineno-2-6"></a><a href="#__codelineno-2-6"><span class="linenos" data-linenos=" 6 "></span></a><span class="s">    // Hover to view descriptions of existing attributes.</span>
<a id="__codelineno-2-7" name="__codelineno-2-7"></a><a href="#__codelineno-2-7"><span class="linenos" data-linenos=" 7 "></span></a><span class="s">    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span>
<a id="__codelineno-2-8" name="__codelineno-2-8"></a><a href="#__codelineno-2-8"><span class="linenos" data-linenos=" 8 "></span></a><span class="s">    &quot;version&quot;: &quot;0.2.0&quot;,</span>
<a id="__codelineno-2-9" name="__codelineno-2-9"></a><a href="#__codelineno-2-9"><span class="linenos" data-linenos=" 9 "></span></a><span class="s">    &quot;configurations&quot;: [</span>
<a id="__codelineno-2-10" name="__codelineno-2-10"></a><a href="#__codelineno-2-10"><span class="linenos" data-linenos="10 "></span></a><span class="s">        {</span>
<a id="__codelineno-2-11" name="__codelineno-2-11"></a><a href="#__codelineno-2-11"><span class="linenos" data-linenos="11 "></span></a><span class="s">            &quot;name&quot;: &quot;Launch Package&quot;,</span>
<a id="__codelineno-2-12" name="__codelineno-2-12"></a><a href="#__codelineno-2-12"><span class="linenos" data-linenos="12 "></span></a><span class="s">            &quot;type&quot;: &quot;go&quot;,</span>
<a id="__codelineno-2-13" name="__codelineno-2-13"></a><a href="#__codelineno-2-13"><span class="linenos" data-linenos="13 "></span></a><span class="s">            &quot;request&quot;: &quot;launch&quot;,</span>
<a id="__codelineno-2-14" name="__codelineno-2-14"></a><a href="#__codelineno-2-14"><span class="linenos" data-linenos="14 "></span></a><span class="s">            &quot;mode&quot;: &quot;debug&quot;,</span>
<a id="__codelineno-2-15" name="__codelineno-2-15"></a><a href="#__codelineno-2-15"><span class="linenos" data-linenos="15 "></span></a><span class="s">            &quot;program&quot;: &quot;${workspaceFolder}/server&quot;,</span>
<a id="__codelineno-2-16" name="__codelineno-2-16"></a><a href="#__codelineno-2-16"><span class="linenos" data-linenos="16 "></span></a><span class="s">            &quot;args&quot;: [&quot;--config-file=/etc/etcd/etcd.conf.yml&quot;]</span>
<a id="__codelineno-2-17" name="__codelineno-2-17"></a><a href="#__codelineno-2-17"><span class="linenos" data-linenos="17 "></span></a><span class="s">        }</span>
<a id="__codelineno-2-18" name="__codelineno-2-18"></a><a href="#__codelineno-2-18"><span class="linenos" data-linenos="18 "></span></a><span class="s">    ]</span>
<a id="__codelineno-2-19" name="__codelineno-2-19"></a><a href="#__codelineno-2-19"><span class="linenos" data-linenos="19 "></span></a><span class="s">}</span>
<a id="__codelineno-2-20" name="__codelineno-2-20"></a><a href="#__codelineno-2-20"><span class="linenos" data-linenos="20 "></span></a><span class="s">EOF</span>
</code></pre></div>
<h3 id="12-etcd">1.2 生成 etcd （仅演示）配置文件<a class="headerlink" href="#12-etcd" title="Permanent link">&para;</a></h3>
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1"></a><a href="#__codelineno-3-1"><span class="linenos" data-linenos=" 1 "></span></a>sudo cat <span class="s">&lt;&lt;-&#39;EOF&#39; &gt; /etc/etcd/etcd.conf.yml</span>
<a id="__codelineno-3-2" name="__codelineno-3-2"></a><a href="#__codelineno-3-2"><span class="linenos" data-linenos=" 2 "></span></a><span class="s">name: &#39;etcd1&#39;</span>
<a id="__codelineno-3-3" name="__codelineno-3-3"></a><a href="#__codelineno-3-3"><span class="linenos" data-linenos=" 3 "></span></a><span class="s">listen-peer-urls: https://10.0.0.123:2380</span>
<a id="__codelineno-3-4" name="__codelineno-3-4"></a><a href="#__codelineno-3-4"><span class="linenos" data-linenos=" 4 "></span></a><span class="s">listen-client-urls: https://10.0.0.123:2379</span>
<a id="__codelineno-3-5" name="__codelineno-3-5"></a><a href="#__codelineno-3-5"><span class="linenos" data-linenos=" 5 "></span></a><span class="s">initial-advertise-peer-urls: https://10.0.0.123:2380</span>
<a id="__codelineno-3-6" name="__codelineno-3-6"></a><a href="#__codelineno-3-6"><span class="linenos" data-linenos=" 6 "></span></a><span class="s">advertise-client-urls: https://10.0.0.123:2379</span>
<a id="__codelineno-3-7" name="__codelineno-3-7"></a><a href="#__codelineno-3-7"><span class="linenos" data-linenos=" 7 "></span></a><span class="s">initial-cluster: etcd1=https://10.0.0.123:2380,etcd2=https://10.0.0.122:2380,etcd3=https://10.0.0.121:2380</span>
<a id="__codelineno-3-8" name="__codelineno-3-8"></a><a href="#__codelineno-3-8"><span class="linenos" data-linenos=" 8 "></span></a><span class="s">initial-cluster-token: &#39;etcd-cluster&#39;</span>
<a id="__codelineno-3-9" name="__codelineno-3-9"></a><a href="#__codelineno-3-9"><span class="linenos" data-linenos=" 9 "></span></a><span class="s"># Initial cluster state (&#39;new&#39; or &#39;existing&#39;).</span>
<a id="__codelineno-3-10" name="__codelineno-3-10"></a><a href="#__codelineno-3-10"><span class="linenos" data-linenos="10 "></span></a><span class="s">initial-cluster-state: &#39;existing&#39;</span>
<a id="__codelineno-3-11" name="__codelineno-3-11"></a><a href="#__codelineno-3-11"><span class="linenos" data-linenos="11 "></span></a><span class="s">EOF</span>
</code></pre></div>
<ul>
<li>从 vscode 启动 etcd server</li>
</ul>
<p><img alt="shot1" src="https://blogs.wl4g.com:443/wp-content/uploads/2021/08/etcd1.png" />
<img alt="shot2" src="https://blogs.wl4g.com:443/wp-content/uploads/2021/08/etcd2.png" /></p>
<h3 id="13">1.3 问题解决<a class="headerlink" href="#13" title="Permanent link">&para;</a></h3>
<p>调试过程就不多说了，这里主要目的是如何搭建etcd的开发调试环境，用以排查线上疑难问题。以上这个演示问题的原因是：新集群需要初始化，所有 <code>etcd.conf.yml</code> 配置的 <code>initial-cluster-state</code> 要设置为 <code>new</code>，而已经初始化了重启之前则最好设置为 <code>existing</code> 以避免其他问题。
说明：如果设置为 <code>existing</code>, 则 <code>etcd</code> 将尝试加入现有群集。如果设置了错误的值, etcd将尝试启动但安全失败。</p>
<hr />
<p>----------------------------------------------- 华丽的分割线 ------------------------------------------------------</p>
<hr />
<h2 id="2">2. 生产部署<a class="headerlink" href="#2" title="Permanent link">&para;</a></h2>
<ul>
<li>集群部署拓扑图：</li>
</ul>
<table>
<thead>
<tr>
<th>IP</th>
<th>Host</th>
<th>FQDN</th>
<th>etcd.conf#<code>etcd.name</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>10.0.0.121</td>
<td>k8s-master-1</td>
<td>n1.etcd.wl4g.uat</td>
<td>etcd1</td>
</tr>
<tr>
<td>10.0.0.122</td>
<td>k8s-master-2</td>
<td>n2.etcd.wl4g.uat</td>
<td>etcd2</td>
</tr>
<tr>
<td>10.0.0.123</td>
<td>k8s-master-3</td>
<td>n3.etcd.wl4g.uat</td>
<td>etcd3</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注：最优雅地对于 etcd 这种 stateful 服务的配置应该是使用域名/主机名，这样可在不同环境 (IP不同) 下轻松迁移，但由于 etcd 限制只能，如 <code>listen-peer-urls</code> 等必须是 IP，<a href="https://github.com/etcd-io/etcd/issues/9575">Validation error: expected IP in URL for binding : https://github.com/etcd-io/etcd/issues/9575</a>，目前不可行（除非改源码）<a href="https://github.com/etcd-io/etcd/blob/release-3.5/server/embed/config.go#L904">https://github.com/etcd-io/etcd/blob/release-3.5/server/embed/config.go#L904</a>。</p>
</blockquote>
<ul>
<li>部署准备</li>
</ul>
<div class="highlight"><pre><span></span><code><a id="__codelineno-4-1" name="__codelineno-4-1"></a><a href="#__codelineno-4-1"><span class="linenos" data-linenos="1 "></span></a>sudo mkdir -p /usr/lib/etcd-current<span class="p">;</span> <span class="nb">cd</span> /usr/lib/etcd-current
<a id="__codelineno-4-2" name="__codelineno-4-2"></a><a href="#__codelineno-4-2"><span class="linenos" data-linenos="2 "></span></a>sudo chmod -R <span class="m">755</span> /usr/lib/etcd-current
<a id="__codelineno-4-3" name="__codelineno-4-3"></a><a href="#__codelineno-4-3"><span class="linenos" data-linenos="3 "></span></a><span class="c1"># github官方包的加速地址</span>
<a id="__codelineno-4-4" name="__codelineno-4-4"></a><a href="#__codelineno-4-4"><span class="linenos" data-linenos="4 "></span></a>curl -O https://github.91chifun.workers.dev/https://github.com//etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz
<a id="__codelineno-4-5" name="__codelineno-4-5"></a><a href="#__codelineno-4-5"><span class="linenos" data-linenos="5 "></span></a>tar -xf etcd-*.tar.gz --strip-components<span class="o">=</span><span class="m">1</span> -C <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>
<a id="__codelineno-4-6" name="__codelineno-4-6"></a><a href="#__codelineno-4-6"><span class="linenos" data-linenos="6 "></span></a>rm -rf etcd-*.tar.gz <span class="c1"># cleanup</span>
<a id="__codelineno-4-7" name="__codelineno-4-7"></a><a href="#__codelineno-4-7"><span class="linenos" data-linenos="7 "></span></a><span class="c1"># link binary.</span>
<a id="__codelineno-4-8" name="__codelineno-4-8"></a><a href="#__codelineno-4-8"><span class="linenos" data-linenos="8 "></span></a>sudo ln -snf <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>/etcd /usr/bin/etcd
<a id="__codelineno-4-9" name="__codelineno-4-9"></a><a href="#__codelineno-4-9"><span class="linenos" data-linenos="9 "></span></a>sudo ln -snf <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>/etcdctl /usr/bin/etcdctl
</code></pre></div>
<h3 id="21">2.1 环境配置<a class="headerlink" href="#21" title="Permanent link">&para;</a></h3>
<blockquote>
<p>注：关于启动参数 <code>ETCD_INITIAL_CLUSTER_STATE</code> 说明：
这是一个无聊的参数。
用于指示这是否是新集群，有两个选项<code>new</code>和<code>existing</code>，如果设为<code>existing</code>，则该member启动时会尝试与其他member交互；集群初次建立时，应设为<code>new</code>，经尝试最后一个节点设为<code>existing</code> 也正常，其他节点不能填为<code>existing</code>；在集群运行过程中，当一个member故障后恢复时填为<code>existing</code>，经尝试设为<code>new</code>也正常。</p>
</blockquote>
<p>源码分析:
<a href="https://github.com/etcd-io/etcd/blob/v3.5.0/server/etcdserver/server.go#L424">https://github.com/etcd-io/etcd/blob/v3.5.0/server/etcdserver/server.go#L424</a></p>
<details style="cursor:pointer">
<summary>/etc/profile.d/profile-etcd.sh</summary>
<pre>
sudo cat <<-'EOF' >/etc/profile.d/profile-etcd.sh
#!/bin/bash
# Copyright (c) 2017 ~ 2025, the original author wangl.sir individual Inc,
# All rights reserved. Contact us <wanglsir@gmail.com, 983708408@qq.com>
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# cat /etc/etcd/etcd.conf | while read etcdVar; do
# if [[ -n "$(echo $etcdVar|sed 's/ //g')" && -z "$(echo $etcdVar|grep -E '^#|^export')" ]]; then
#   etcdVarName=$(echo $etcdVar|awk -F '=' '{print $1}'|sed 's/ //g')
#   etcdVarVal=$(echo $etcdVar|awk -F '=' '{print $2}'|sed 's/ //g')
#   eval "export $etcdVarName=\"$etcdVarVal\""
# fi
# done

. /etc/etcd/etcd.conf
export PATH=$PATH:/usr/lib/etcd-current

# This is a foolish parameter.
# Used to indicate whether this is a new cluster. There are two values, 'new' and 'existing'. 
# If it is set to 'existing', the member will try to interact with other members when starting.
# When creating a cluster for the first time, it should be set to 'new'. After trying, it is 
# normal for the last node to fill in 'existing', and other nodes cannot be set to 'existing'
# During cluster operation, when a member recovers after failure, it is set to 'existing', 
# and it is normal to try to set it to 'new'.
#
# see: https://etcd.io/docs/v3.5/op-guide/configuration/#--initial-cluster-state.
# see: https://github.com/etcd-io/etcd/blob/v3.5.0/server/etcdserver/server.go#L424
# see: https://my.oschina.net/u/160697/blog/4283750
#
# The naming rule: etcd1/etcd2/etcd3, the default set first 'etcd1' is leader.
#IS_LEADER="$([ "$ETCD_NAME" == "etcd1" ] && echo Y || echo N)"
#if [[ $IS_LEADER == 'Y' && -f "$ETCD_DATA_DIR/member/snap/db" ]]; then
if [[ -f "$ETCD_DATA_DIR/member/snap/db" ]]; then
  export ETCD_INITIAL_CLUSTER_STATE='new'
else
  export ETCD_INITIAL_CLUSTER_STATE='existing'
fi

# [Additional] log directory. see: /etc/init.d/etcd.sh
export ETCD_LOG_DIR=/mnt/disk1/log/etcd
EOF

. /etc/profile.d/profile-etcd.sh
sudo chmod +x /etc/profile.d/profile-etcd.sh
</pre>
</details>

<h3 id="22">2.2 运行配置<a class="headerlink" href="#22" title="Permanent link">&para;</a></h3>
<blockquote>
<p>注：以下为主节点 <code>etcd1</code> 配置，其他从节点需自行修改 <code>ETCD_NAME=etcd2</code> 或 <code>ETCD_NAME=etcd3</code> 等，其他地址相关环境变量配置 <code>ETCD_LISTEN_PEER_URLS</code>、<code>ETCD_LISTEN_CLIENT_URLS</code>、<code>ETCD_INITIAL_ADVERTISE_PEER_URLS</code>、<code>ETCD_ADVERTISE_CLIENT_URLS</code> 同理按实际 IP 修改。</p>
</blockquote>
<details style="cursor:pointer">
<summary>/etc/profile.d/profile-etcd.sh</summary>
<pre>
sudo mkdir -p /etc/etcd
sudo cat <<-'EOF' >/etc/etcd/etcd.conf
# Copyright (c) 2017 ~ 2025, the original author wangl.sir individual Inc,
# All rights reserved. Contact us <wanglsir@gmail.com, 983708408@qq.com>
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Refer to see: https://etcd.io/docs/v3.5/op-guide/configuration
#

# Member environoments.
export ETCD_NAME='etcd1'
export ETCD_DATA_DIR='/mnt/disk1/etcd'
# The dedicated wal directory. (default={data-dir}/wal)
export ETCD_WAL_DIR="${ETCD_DATA_DIR}/wal"
export ETCD_SNAPSHOT_COUNT=10000
export ETCD_HEARTBEAT_INTERVAL=200
export ETCD_ELECTION_TIMEOUT=5000
export ETCD_LISTEN_PEER_URLS='https://10.0.0.121:2380'
export ETCD_LISTEN_CLIENT_URLS='https://0.0.0.0:2379'
export ETCD_MAX_SNAPSHOTS=10
export ETCD_MAX_WALS=10
export ETCD_CORS='*'
export ETCD_QUOTA_BACKEND_BYTES=0
export ETCD_BACKEND_BATCH_LIMIT=0
export ETCD_BACKEND_BBOLT_FREELIST_TYPE=map
export ETCD_BACKEND_BATCH_INTERVAL=0
export ETCD_MAX_TXN_OPS=128
export ETCD_MAX_REQUEST_BYTES=1572864
export ETCD_GRPC_KEEPALIVE_MIN_TIME=5s
export ETCD_GRPC_KEEPALIVE_INTERVAL=2h
export ETCD_GRPC_KEEPALIVE_TIMEOUT=20s

# Clustering environments.
# The etcd cluster runs URLs configuration. Notice=there are differences between leader and follower nodes.
export ETCD_INITIAL_ADVERTISE_PEER_URLS='https://10.0.0.121:2380'
export ETCD_INITIAL_CLUSTER='etcd1=https://10.0.0.121:2380,etcd2=https://10.0.0.122:2380,etcd3=https://10.0.0.123:2380'
# The initial startup must be 'new', and the subsequent startup must be 'existing'. (default='new')
#export ETCD_INITIAL_CLUSTER_STATE='new'
export ETCD_INITIAL_CLUSTER_TOKEN='etcd-cluster'
export ETCD_ADVERTISE_CLIENT_URLS='https://10.0.0.121:2379'
#export ETCD_DISCOVERY=''
#export ETCD_DISCOVERY_PROXY=''
#export ETCD_DISCOVERY_SRV=''
#export ETCD_DISCOVERY_SRV_NAME=''
#export ETCD_DISCOVERY_FALLBACK='proxy'
#export ETCD_DISCOVERY_PROXY=''
export ETCD_STRICT_RECONFIG_CHECK=false
export ETCD_AUTO_COMPACTION_MODE=periodic
export ETCD_AUTO_COMPACTION_RETENTION=8
export ETCD_ENABLE_V2=false

# Profiling environments.
export ETCD_ENABLE_PPROF=true
export ETCD_METRICS='basic'
#export ETCD_LISTEN_METRICS_URLS=''

# Proxy environments.
export ETCD_PROXY='off'
export ETCD_PROXY_FAILURE_WAIT=5000
export ETCD_PROXY_REFRESH_INTERVAL=30000
export ETCD_PROXY_DIAL_TIMEOUT=1000
export ETCD_PROXY_WRITE_TIMEOUT=5000
export ETCD_PROXY_READ_TIMEOUT=0

# Auth environments.
#export ETCD_AUTH_TOKEN='simple'

# Security environments.
export ETCD_CERT_FILE='/etc/etcd/ssl/etcd.pem'
export ETCD_KEY_FILE='/etc/etcd/ssl/etcd-key.pem'
#export ETCD_CLIENT_CERT_AUTH=''
#export ETCD_CLIENT_CRL_FILE=''
#export ETCD_CLIENT_CERT_ALLOWED_HOSTNAME=''
export ETCD_TRUSTED_CA_FILE='/etc/etcd/ssl/ca.pem'
#export ETCD_AUTO_TLS=false
export ETCD_PEER_CERT_FILE='/etc/etcd/ssl/etcd.pem'
export ETCD_PEER_KEY_FILE='/etc/etcd/ssl/etcd-key.pem'
#export ETCD_PEER_CLIENT_CERT_AUTH=false
#export ETCD_PEER_CRL_FILE=''
export ETCD_PEER_TRUSTED_CA_FILE='/etc/etcd/ssl/ca.pem'
#export ETCD_PEER_AUTO_TLS=false
#export ETCD_PEER_CERT_ALLOWED_CN=''
#export ETCD_PEER_CERT_ALLOWED_HOSTNAME=''
# Notice: etcdctl Ssl handshake failed.
# [issue]: https://github.com/etcd-io/etcd/issues/9785
# [issue]: https://github.com/etcd-io/etcd/issues/10652
#export ETCD_CIPHER_SUITES=''

# Logging environments.
export ETCD_LOGGER=zap
# Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd.
export ETCD_LOG_OUTPUTS='stderr,stdout'
export ETCD_LOG_LEVEL=debug

# Experimental environments.
#export ETCD_EXPERIMENTAL_CORRUPT_CHECK_TIME='0s'
#export ETCD_EXPERIMENTAL_COMPACTION_BATCH_LIMIT=1000
#export ETCD_EXPERIMENTAL_PEER_SKIP_CLIENT_SAN_VERIFICATION=false

# Miscellaneous environments.
#export ETCD_CONFIG_FILE=''

# Unsafe environments.
export ETCD_FORCE_NEW_CLUSTER=false

# Etcdctl environments.
export ETCDCTL_ENDPOINTS=https://10.0.0.121:2379,https://10.0.0.122:2379,https://10.0.0.123:2379
export ETCDCTL_CACERT=/etc/etcd/ssl/ca.pem
export ETCDCTL_CERT=/etc/etcd/ssl/etcd.pem
export ETCDCTL_KEY=/etc/etcd/ssl/etcd-key.pem
EOF
</pre>
</details>

<h3 id="23">2.3 管理脚本<a class="headerlink" href="#23" title="Permanent link">&para;</a></h3>
<blockquote>
<p>可兼容无 <code>systemd</code> 的 Linux 低版本系统，如 CentOS 6</p>
</blockquote>
<details style="cursor:pointer">
<summary>/etc/init.d/etcd.sh</summary>
<pre>
sudo cat <<-'EOF' >/etc/init.d/etcd.sh
#!/bin/bash
# chkconfig: - 85 15
#/*
# * Copyright 2017 ~ 2025 the original author or authors. &lt;Wanglsir@gmail.com, 983708408@qq.com&gt;
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# */

[ -f /etc/sysconfig/network ] && . /etc/sysconfig/network
[ "$NETWORKING" = "no" ] && exit 0

# Load the user environment.
[ -f "/etc/profile.d/profile-etcd.sh" ] && . /etc/profile.d/profile-etcd.sh
[ -f "/etc/bashrc" ] && . /etc/bashrc
[ -f "/etc/bash.bashrc" ] && . /etc/bash.bashrc # e.g ubuntu
[ -f "/home/$USER/.bash_profile" ] && . /home/$USER/.bash_profile
[ -f "/home/$USER/.bashrc" ] && . /home/$USER/.bashrc
# Mac OS
[ -f "/Users/$USER/.bash_profile" ] && . /Users/$USER/.bash_profile
[ -f "/Users/$USER/.bashrc" ] && . /Users/$USER/.bashrc

# Environment definition.
etcdBin="$(command -v etcd)"
etcdLogDir="${ETCD_LOG_DIR:-/mnt/disk1/log/etcd}"

function start() {
  local pids=$(getPids)
  if [ -z "$pids" ]; then
    # Notice: if '--config-file' is used configuration here, the environment variables
    # and startup parameters will become invalid. It is not recommended.
    if [ "$PPID" == "1" ]; then # Systemd call.
      "$etcdBin" > "$etcdLogDir/etcd.stdout" 2>&1
    else # Normal shell call.
      nohup "$etcdBin" > "$etcdLogDir/etcd.stdout" 2>&1 &
    fi

    echo -n "Starting etcd ..."
    while true
    do
      pids=$(getPids)
      if [ "$pids" == "" ]; then
        echo -n ".";
        sleep 0.8;
      else
        echo $pids >"$ETCD_DATA_DIR/etcd.pid"
        break
      fi
    done
    echo -e "\nStarted etcd on "$pids
  else
    echo "etcd process is running "$pids
  fi
}

function stop() {
  local pids=$(getPids)
  if [ -z "$pids" ]; then
    echo "etcd not running!"
  else
    echo -n "Stopping etcd for $pids ..."
    kill -s TERM $pids
    while true
    do
      pids=$(getPids)
      if [ "$pids" == "" ]; then
        \rm -f $ETCD_DATA_DIR/etcd.pid
        break
      else
        echo -n ".";
        sleep 0.8;
      fi
    done
    echo -e "\nStopped etcd !"
  fi
}

function status() {
  ps -ef | grep -v grep | grep $etcdBin
}

function getPids() {
  local pids=$(ps ax | grep -i "$etcdBin" | grep -v grep | awk '{print $1}')
  echo $pids # Output execution result value.
  return 0 # Return the execution result code.
}

# --- Main call. ---
CMD=$1
case $CMD in
  status)
    status
    ;;
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    stop
    start
    ;;
    *)
  echo $"Usage: {start|stop|restart|status}"
  exit 2
esac
EOF

. /etc/profile

sudo useradd etcd
sudo chown -R etcd:etcd /etc/init.d/etcd.sh
sudo chmod +x /etc/init.d/etcd.sh

sudo mkdir -p $ETCD_LOG_DIR
sudo chown -R etcd:etcd $ETCD_LOG_DIR

sudo mkdir -p $ETCD_DATA_DIR
sudo chown -R etcd:etcd $ETCD_DATA_DIR
# Etcd 推荐值(The recommended permission is \"-rwx------\" to prevent possible unprivileged access to the data)
sudo chmod 700 $ETCD_DATA_DIR
</pre>
</details>

<h3 id="24">2.4 服务配置<a class="headerlink" href="#24" title="Permanent link">&para;</a></h3>
<details style="cursor:pointer">
<summary>/etc/systemd/system/etcd.service</summary>
<pre>
sudo cat <<-'EOF' >/etc/systemd/system/etcd.service
# Copyright (c) 2017 ~ 2025, the original author wangl.sir individual Inc,
# All rights reserved. Contact us <wanglsir@gmail.com, 983708408@qq.com>
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/bin/bash -c "/etc/init.d/etcd.sh start"
ExecReload=/bin/bash -c "/etc/init.d/etcd.sh restart"
ExecStop=/bin/bash -c "/etc/init.d/etcd.sh stop"
StandardOutput=null
StandardError=journal
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=5
Restart=always
KillMode=process
User=etcd
Group=etcd
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable etcd
</pre>
</details>

<h3 id="25">2.5 配置证书<a class="headerlink" href="#25" title="Permanent link">&para;</a></h3>
<p>生成证书（只需在 10.0.0.121 / k8s-master-1 上执行，然后通过 <code>scp -r /etc/etcd/ssl k8s-master-2:/etc/etcd/</code> 拷贝分发即可）</p>
<details style="cursor:pointer">
<summary>/etc/etcd/ssl</summary>
<pre>
sudo curl -o /bin/cfssl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_linux_amd64
sudo curl -o /bin/cfssljson -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_linux_amd64
sudo chmod +x /bin/cfssl
sudo chmod +x /bin/cfssljson
#或 sudo apt install golang-cfssl
#
sudo mkdir /etc/etcd/ssl
#
# Generating config.
sudo cat <<-'EOF' >config.json
{"signing":{"default":{"expiry":"87600h"},"profiles":{"etcd-cluster-1":{"usages":["signing","key encipherment","server auth","client auth"],"expiry":"87600h"}}}}
EOF
#
# Generating CA certificate singing request config.
sudo cat <<-'EOF' >ca-csr.json
{"CN":"WL4G Root CA cert issuer","CA":{"expiry":"87600h","pathlen":0},"key":{"algo":"rsa","size":2048},"names":[{"C":"US","L":"San Francisco 12th street","O":"WL4G company, Inc.","OU":"www dept","ST":"California"}]}
EOF
#
# Generating etcd certificate singing request config.
sudo cat <<-'EOF' >etcd-csr.json
{"hosts":["10.0.0.121","10.0.0.122","10.0.0.123","k8s-master-1","k8s-master-2","k8s-master-3","https://etcd.wl4g.uat","https://n1.etcd.wl4g.uat","https://n2.etcd.wl4g.uat","https://n3.etcd.wl4g.uat","127.0.0.1"],"CN":"wl4g.uat","key":{"algo":"rsa","size":2048},"names":[{"C":"CN","L":"GuangZhou 6th street","O":"SM, Inc.","OU":"WWW dept","ST":"GuangDong"}]}
EOF
#
# Generating CA certificate.
sudo cfssl genkey -initca ca-csr.json | cfssljson -bare ca
#
# Generating etcd certificate.
sudo cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config.json -profile=etcd-cluster-1 etcd-csr.json | cfssljson -bare etcd
#
# Print CA and etcd certificate.
sudo openssl x509 -in etcd-key.pem -noout -text
sudo openssl x509 -in etcd.pem -noout -text
#
# Copy to other nodes directory.
sudo scp -r  /etc/etcd/ssl k8s-master-2:/etc/etcd
sudo scp -r  /etc/etcd/ssl k8s-master-3:/etc/etcd
sudo scp -r  /etc/etcd/ssl k8s-worker-1:/etc/etcd
</pre>
</details>

<h3 id="26-etcd">2.6 启动 <code>etcd</code> 集群并验证<a class="headerlink" href="#26-etcd" title="Permanent link">&para;</a></h3>
<details style="cursor:pointer">
<summary>使用 etcdctl endpoint status -w table 验证</summary>
<pre>
# n1.etcd.wl4g.uat
sudo systemctl restart etcd
# n2.etcd.wl4g.uat
sudo systemctl restart etcd
# n3.etcd.wl4g.uat
sudo systemctl restart etcd
# 查看日志
sudo tail -f /mnt/disk1/log/etcd.stdout
#
# 在任意一台执行 etcdctl 查看集群状态
etcdctl endpoint status -w table --endpoints https://10.0.0.121:2379,https://10.0.0.122:2379,https://10.0.0.123:2379 --cacert /etc/etcd/ssl/ca.pem --cert /etc/etcd/ssl/etcd.pem --key /etc/etcd/ssl/etcd-key.pem
#
# 或直接这样干掉一大串（已配置在 /etc/etcd/etcd.conf 中，ETCDCTL_ 前缀）
etcdctl endpoint status -w table
+-------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|        ENDPOINT         |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+-------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| https://10.0.0.121:2379 | e6e9b682e792c054 |   3.5.0 |   20 kB |     false |      false |         3 |         11 |                 11 |        |
| https://10.0.0.122:2379 | 79378de21f8ee3a3 |   3.5.0 |   25 kB |      true |      false |         3 |         11 |                 11 |        |
| https://10.0.0.123:2379 | aafe9b852edcd7e6 |   3.5.0 |   29 kB |     false |      false |         3 |         11 |                 11 |        |
+-------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
</pre>
</details>

<h2 id="3-backup-data-snaphots">3. Backup data snaphots.<a class="headerlink" href="#3-backup-data-snaphots" title="Permanent link">&para;</a></h2>
<blockquote>
<p>Notice: <code>etcdctl</code> backup can only connect one node at a time. </p>
</blockquote>
<details style="cursor:pointer">
<summary>/etc/etcd/etcd-backup.sh</summary><pre>
sudo cat <<-'EOF' >/etc/etcd/etcd-backup.sh
#!/bin/bash
# Copyright (c) 2017 ~ 2025, the original author wangl.sir individual Inc,
# All rights reserved. Contact us <wanglsir@gmail.com, 983708408@qq.com>
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -e

# Load environments. (include: /etc/profile.d/profile-etcd.sh, /etc/etcd/etcd.conf)
. /etc/profile

# Global definition.
export backupEndpoint='https://10.0.0.121:2379'
export backupMaxFiles='8'
export backupDir="${ETCD_DATA_DIR}/backups"; mkdir -p $backupDir
export backupLogFile="$backupDir/backup.log"
export backupLogMaxBytes=$((10*1024*1024*1024)) # 10GB
export backupCurrentFile="${backupDir}/snapshot-$(date +%Y%m%d_%H%M%S).db"

function doBackupSnapshot() {
  # Tmp tranform etcdctl endpoints environment.
  local originEtcdctlEndpoints=$ETCDCTL_ENDPOINTS
  export ETCDCTL_ENDPOINTS=$backupEndpoint
  {
    echo "$(date +%Y-%m-%d_%H:%M:%S) - Backuping for $backupEndpoint ..."
    etcdctl snapshot save "$backupCurrentFile"
    echo "$(date +%Y-%m-%d_%H:%M:%S) - Success backup to $backupCurrentFile on $backupEndpoint"
  } 2>&1 | tee -a "$backupLogFile" 
  # Restore etcdctl endpoints environments
  export ETCDCTL_ENDPOINTS=$originEtcdctlEndpoints
}

function doCleanup() {
  # Check older snapshots.
  echo "$(date +%Y-%m-%d_%H:%M:%S) - Cleaning older backup snapshot files ..." | tee -a "$backupLogFile"
  cd $backupDir/;ls -lt |awk '{if(NR > '$backupMaxFiles'){print "rm -rf "$9}}' | sh

  # Check backup log.
  local logSize=`ls -l $backupLogFile | awk '{ print $5 }'`
  if [ $logSize -gt $backupLogMaxBytes ]; then
    echo > "$backupLogFile"
    echo "$(date +%Y-%m-%d_%H:%M:%S) - Cleaned backup log." | tee -a "$backupLogFile"
  fi
}

# --- Main. ---
doBackupSnapshot
doCleanup
EOF

sudo chmod +x /etc/etcd/etcd-backup.sh

# Add backup to crontab.
echo "*/5  *  *  *  * root  /etc/etcd/etcd-backup.sh" >> /etc/crontab
# Add kill zombie process.
echo "  0  3  *  *  * root  /bin/ps -A -ostat,ppid | grep -e '^[Zz]' | awk '{print }' | xargs kill -HUP > /dev/null 2>&1" >> /etc/crontab

sudo systemctl restart crond
sudo tail -f /var/log/cron
# 或 sudo journalctl -u crond -f
</pre>
</details>

<h2 id="4-refer">4. Refer<a class="headerlink" href="#4-refer" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="https://etcd.io/docs/v3.5/op-guide/configuration">更多 Etcd flags 参数名与环境变量名对应表</a></li>
</ul>

              
            </article>
          </div>
        </div>
        
      </main>
      
        <footer class="md-footer">
  
  <div class="md-footer-meta md-typeset">
    <div class="md-footer-meta__inner md-grid">
      <div class="md-copyright">
  
  
    Made with
    <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
      Material for MkDocs
    </a>
  
</div>
      
    </div>
  </div>
</footer>
      
    </div>
    <div class="md-dialog" data-md-component="dialog">
      <div class="md-dialog__inner md-typeset"></div>
    </div>
    <script id="__config" type="application/json">{"base": "../../../..", "features": ["search.suggest", "search.highlight", "navigation.tabs", "navigation.expand", "toc.follow", "toc.integrate"], "search": "../../../../assets/javascripts/workers/search.5e67fbfe.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version.title": "Select version"}, "version": {"default": "latest", "provider": "mike"}}</script>
    
    
      <script src="../../../../assets/javascripts/bundle.c44cc438.min.js"></script>
      
        <script src="../../../../static/js/util.js"></script>
      
    
  </body>
</html>